package com.hk.commons.util;

import com.hk.commons.cglib.BeanConverter;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.FatalBeanException;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 与Bean相关的工具类
 *
 * @author kevin
 * @date 2017年9月12日上午10:01:39
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class BeanUtils {

    /**
     * Map To Bean
     *
     * @param map   map
     * @param clazz clazz
     * @param <T>   T
     * @return T
     */
    @SneakyThrows
    public static <T> T mapToBean(Map<String, ?> map, Class<T> clazz) {
        var beanWrapper = PropertyAccessorUtils.forBeanPropertyAccess(clazz);
        for (var entry : map.entrySet()) {
            if (beanWrapper.isWritableProperty(entry.getKey())) {
                beanWrapper.setPropertyValue(entry.getKey(), entry.getValue());
            }
        }
        return clazz.cast(beanWrapper.getWrappedInstance());
    }

    /**
     * Bean to Map
     *
     * @param obj              obj
     * @param ignoreProperties ignoreProperties
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object obj, String... ignoreProperties) {
        return beanToMap(obj, false, ignoreProperties);
    }

    /**
     * Bean to Map
     *
     * @param obj               obj
     * @param containsNullValue containsNullValue
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object obj, boolean containsNullValue, String... ignoreProperties) {
        var result = new HashMap<String, Object>();
        if (Objects.nonNull(obj)) {
            BeanMap beanMap = BeanMap.create(obj);
            for (Object object : beanMap.entrySet()) {
                if (object instanceof Map.Entry<?, ?> entry) {
                    String field = (String) entry.getKey();
                    Object value = entry.getValue();
                    if ((Objects.nonNull(value) || containsNullValue) && ArrayUtils.noContains(ignoreProperties, field)) {
                        result.put(field, value);
                    }
                }
            }
        }
        return result;
    }

    /**
     * <pre>
     *
     * 将源对象中的不为空的值拷贝到 target对象中
     * 使用此方法进行拷贝时，类型必须一样，基本类型也必须一样，否则拷贝不成功。如 int -> Integer 不会拷贝成功
     *
     * </pre>
     *
     * @param source 源对象
     * @param target 拷贝后的对象
     */
    public static void copierCopyNotNullProperties(Object source, Object target) {
        var beanCopier = BeanCopier.create(source.getClass(), target.getClass(), true);
        beanCopier.copy(source, target, new BeanConverter(source));
    }

    /**
     * 复制不为空的属性
     *
     * @param source source
     * @param target target
     */
    public static void copyNotNullProperties(Object source, Object target) {
        var targetDescriptors = org.springframework.beans.BeanUtils.getPropertyDescriptors(target.getClass());
        for (var targetDescriptor : targetDescriptors) {
            var writeMethod = targetDescriptor.getWriteMethod();
            if (Objects.nonNull(writeMethod)) {
                var sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(source.getClass(), targetDescriptor.getName());
                if (null != sourcePd) {
                    var readMethod = sourcePd.getReadMethod();
                    if (Objects.nonNull(readMethod) &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            var value = readMethod.invoke(source);
                            if (ObjectUtils.isNotEmpty(value)) {
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                writeMethod.invoke(target, value);
                            }
                        } catch (Throwable ex) {
                            throw new FatalBeanException(
                                    STR."Could not copy property '\{targetDescriptor.getName()}' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

}
